/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.recyclerview.widget;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;

import java.util.Arrays;

public class MultiGridLayoutManager extends CopyLinearLayoutManager {

    private static final boolean DEBUG = true;
    private static final String TAG = "GridLayoutManager";
    /**
     * Span size have been changed but we've not done a new layout calculation.
     */
    boolean mPendingSpanCountChange = false;
    /**
     * 主轴span
     */
    SpanSize mSpanSize = new SpanSize(1, 1);
    /**
     * 布局每个span 的平分位置，比如控件宽度1080 span=2 返回值为[0,540,1080]
     * Right borders for each span.
     * <p>For <b>i-th</b> item start is {@link #mCachedBorders}[i-1] + 1
     * and end is {@link #mCachedBorders}[i].
     */
    int[] mCachedBorders;
    /**
     * size=spanMain*spanSecond [Mian1Second1,Mian1Second2,Mian1Second3,,Mian2Second1 ……]
     * Temporary array to keep views in layoutChunk method
     */
    View[] mSet;
    final SparseArray<SpanIndexParams> mPreLayoutCache = new SparseArray<>();
    SpanSizeLookup mSpanSizeLookup = new SpanSizeLookup() {
        @Override
        protected SpanSize getSpanSize(int position) {
            return new SpanSize(1, 1);
        }
    };
    // re-used variable to acquire decor insets from RecyclerView
    final Rect mDecorInsets = new Rect();

    /**
     * Creates a vertical GridLayoutManager
     *
     * @param context Current context, will be used to access resources.
     * @param span    The number of columns in the grid
     */
    public MultiGridLayoutManager(Context context, SpanSize span) {
        super(context);
        setSpanCount(span);
    }

    /**
     * @param context       Current context, will be used to access resources.
     * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link
     *                      #VERTICAL}.
     * @param reverseLayout When set to true, layouts from end to start.
     */
    public MultiGridLayoutManager(Context context, SpanSize span, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        setSpanCount(span);
    }

    public RecyclerView getRecyclerView() {
        return mRecyclerView;
    }

    /**
     * stackFromEnd is not supported by MultiGridLayoutManager. Consider using
     * {@link #setReverseLayout(boolean)}.
     */
    @Override
    public final void setStackFromEnd(boolean stackFromEnd) {
        if (stackFromEnd) {
            throw new UnsupportedOperationException("GridLayoutManager does not support stack from end." + " Consider using reverse layout");
        }
        super.setStackFromEnd(false);
    }

    @Override
    public int getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return mSpanSize.main;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }

        // Row count is one more than the last item's row index.
        return getSpanIndex(recycler, state, state.getItemCount() - 1).groupIndex + 1;
    }

    @Override
    public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return mSpanSize.main;
        }
        if (state.getItemCount() < 1) {
            return 0;
        }

        // Column count is one more than the last item's column index.
        return getSpanIndex(recycler, state, state.getItemCount() - 1).groupIndex + 1;
    }

    @Override
    public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
        ViewGroup.LayoutParams lp = host.getLayoutParams();
        if (!(lp instanceof LayoutParams)) {
            super.onInitializeAccessibilityNodeInfoForItem(host, info);
            return;
        }
        LayoutParams glp = (LayoutParams) lp;
        SpanIndexParams spanSize = glp.mSpanIndex;
        if (mOrientation == HORIZONTAL) {
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(spanSize.mainIndex, spanSize.mainSpan, spanSize.groupIndex, spanSize.secondIndex + spanSize.secondSpan, false, false));
        } else { // VERTICAL
            info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(spanSize.groupIndex, spanSize.secondIndex + spanSize.secondSpan, spanSize.mainIndex, spanSize.mainSpan, false, false));
        }
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.isPreLayout()) {
            cachePreLayoutSpanMapping();
        }
        super.onLayoutChildren(recycler, state);
        if (DEBUG) {
            validateChildOrder();
        }
        clearPreLayoutSpanMappingCache();
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        mPendingSpanCountChange = false;
    }

    private void clearPreLayoutSpanMappingCache() {
        mPreLayoutCache.clear();
    }

    private void cachePreLayoutSpanMapping() {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
            final int viewPosition = lp.getViewLayoutPosition();
            mPreLayoutCache.put(viewPosition, lp.mSpanIndex);
        }
    }

    @Override
    public void onAdapterChanged(@Nullable RecyclerView.Adapter oldAdapter, @Nullable RecyclerView.Adapter newAdapter) {
        super.onAdapterChanged(oldAdapter, newAdapter);
        mRecyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) {
        super.prepareForDrop(view, target, x, y);
        mRecyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
        recyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void onItemsChanged(RecyclerView recyclerView) {
        recyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
        recyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount, Object payload) {
        recyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
        recyclerView.markItemDecorInsetsDirty();
        mSpanSizeLookup.invalidateCache();
    }

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
        } else {
            return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }

    @Override
    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
        return new LayoutParams(c, attrs);
    }

    @Override
    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        if (lp instanceof ViewGroup.MarginLayoutParams) {
            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
        } else {
            return new LayoutParams(lp);
        }
    }

    @Override
    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
        return lp instanceof LayoutParams;
    }

    /**
     * Sets the source to get the number of spans occupied by each item in the adapter.
     *
     * @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
     *                       occupied by each item
     */
    public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
        mSpanSizeLookup = spanSizeLookup;
        if (spanSizeLookup != null) {
            spanSizeLookup.layoutManager = this;
        }
    }

    /**
     * Returns the current {@link SpanSizeLookup} used by the MultiGridLayoutManager.
     *
     * @return The current {@link SpanSizeLookup} used by the MultiGridLayoutManager.
     */
    public SpanSizeLookup getSpanSizeLookup() {
        return mSpanSizeLookup;
    }

    private void updateMeasurements() {
        int totalSpace;
        if (getOrientation() == VERTICAL) {
            totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
        } else {
            totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
        }
        calculateItemBorders(totalSpace);
    }

    @Override
    public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
        if (mCachedBorders == null) {
            super.setMeasuredDimension(childrenBounds, wSpec, hSpec);
        }
        final int width, height;
        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
        final int verticalPadding = getPaddingTop() + getPaddingBottom();
        if (mOrientation == VERTICAL) {
            final int usedHeight = childrenBounds.height() + verticalPadding;
            height = chooseSize(hSpec, usedHeight, getMinimumHeight());
            width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding, getMinimumWidth());
        } else {
            final int usedWidth = childrenBounds.width() + horizontalPadding;
            width = chooseSize(wSpec, usedWidth, getMinimumWidth());
            height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding, getMinimumHeight());
        }
        setMeasuredDimension(width, height);
    }

    /**
     * @param totalSpace Total available space after padding is removed
     */
    private void calculateItemBorders(int totalSpace) {
        mCachedBorders = calculateItemBorders(mCachedBorders, mSpanSize.main, totalSpace);
    }

    private int[] calculateSecondBorders(int span, int totalSpace) {
        return calculateItemBorders(null, span, totalSpace);
    }

    /**
     * 布局每个span 的平分位置，比如控件宽度1080 span=2 返回值为[0,540,1080]
     *
     * @param cachedBorders The out array
     * @param span          number of spans
     * @param totalSpace    total available space after padding is removed
     * @return The updated array. Might be the same instance as the provided array if its size
     * has not changed.
     */
    static int[] calculateItemBorders(int[] cachedBorders, int span, int totalSpace) {
        if (cachedBorders == null || cachedBorders.length != span + 1 || cachedBorders[cachedBorders.length - 1] != totalSpace) {
            cachedBorders = new int[span + 1];
        }
        cachedBorders[0] = 0;
        int sizePerSpan = totalSpace / span;
        int sizePerSpanRemainder = totalSpace % span;
        int consumedPixels = 0;
        int additionalSize = 0;
        for (int i = 1; i <= span; i++) {
            int itemSize = sizePerSpan;
            additionalSize += sizePerSpanRemainder;
            if (additionalSize > 0 && (span - additionalSize) < sizePerSpanRemainder) {
                itemSize += 1;
                additionalSize -= span;
            }
            consumedPixels += itemSize;
            cachedBorders[i] = consumedPixels;
        }
        return cachedBorders;
    }

    int getSpaceForSpanRange(int startSpan, int spanSize) {
        if (mOrientation == VERTICAL && isLayoutRTL()) {
            return mCachedBorders[mSpanSize.main - startSpan] - mCachedBorders[mSpanSize.main - startSpan - spanSize];
        } else {
            return mCachedBorders[startSpan + spanSize] - mCachedBorders[startSpan];
        }
    }

    @Override
    void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
        super.onAnchorReady(recycler, state, anchorInfo, itemDirection);
        updateMeasurements();
        if (state.getItemCount() > 0 && !state.isPreLayout()) {
            ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection);
        }
        ensureViewSet();
    }

    /**
     * 拖动后会调用到这里，找到 chunk 的第一个item
     */
    private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
        final boolean layingOutInPrimaryDirection = itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
        SpanIndexParams span = getSpanIndex(recycler, state, anchorInfo.mPosition);
        if (layingOutInPrimaryDirection) {
            // choose span 0
            while ((span.mainIndex > 0 || span.secondIndex > 0) && anchorInfo.mPosition > 0) {//找到第一个item
                anchorInfo.mPosition--;
                span = getSpanIndex(recycler, state, anchorInfo.mPosition);
            }
        } else {//这个逻辑没测到
            // choose the max span we can get. hopefully last one
            final int indexLimit = state.getItemCount() - 1;
            int pos = anchorInfo.mPosition;
            SpanIndexParams bestSpan = span;
            while (pos < indexLimit) {
                SpanIndexParams next = getSpanIndex(recycler, state, pos + 1);
                if (next.mainIndex > bestSpan.mainIndex) {
                    pos += 1;
                    bestSpan = next;
                } else {
                    break;
                }
            }
            anchorInfo.mPosition = pos;
        }
    }

    private void ensureViewSet() {
        int size = getSpanCount();
        if (mSet == null || mSet.length != size) {
            mSet = new View[size];
        }
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
        updateMeasurements();
        ensureViewSet();
        return super.scrollHorizontallyBy(dx, recycler, state);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        updateMeasurements();
        ensureViewSet();
        return super.scrollVerticallyBy(dy, recycler, state);
    }

    @Override
    View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state, boolean layoutFromEnd, boolean traverseChildrenInReverseOrder) {

        int start = 0;
        int end = getChildCount();
        int diff = 1;
        if (traverseChildrenInReverseOrder) {
            start = getChildCount() - 1;
            end = -1;
            diff = -1;
        }

        int itemCount = state.getItemCount();

        ensureLayoutState();
        View invalidMatch = null;
        View outOfBoundsMatch = null;

        final int boundsStart = mOrientationHelper.getStartAfterPadding();
        final int boundsEnd = mOrientationHelper.getEndAfterPadding();

        for (int i = start; i != end; i += diff) {
            final View view = getChildAt(i);
            final int position = getPosition(view);
            if (position >= 0 && position < itemCount) {
                final SpanIndexParams span = getSpanIndex(recycler, state, position);
                if (span.mainIndex != 0) {
                    continue;
                }
                if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
                    if (invalidMatch == null) {
                        invalidMatch = view; // removed item, least preferred
                    }
                } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
                    if (outOfBoundsMatch == null) {
                        outOfBoundsMatch = view; // item is not visible, less preferred
                    }
                } else {
                    return view;
                }
            }
        }
        return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
    }

    private SpanIndexParams getSpanIndex(RecyclerView.Recycler recycler, RecyclerView.State state, int pos) {
        if (!state.isPreLayout()) {
            return mSpanSizeLookup.getCachedSpanIndex(pos);
        }
        final SpanIndexParams cached = mPreLayoutCache.get(pos, null);
        if (cached != null) {
            return cached;
        }
        final int adapterPosition = recycler.convertPreLayoutPositionToPostLayout(pos);
        if (adapterPosition == -1) {
            if (DEBUG) {
                throw new RuntimeException("Cannot find span index for pre layout position. It is" + " not cached, not in the adapter. Pos:" + pos);
            }
            Log.w(TAG, "Cannot find span size for pre layout position. It is" + " not cached, not in the adapter. Pos:" + pos);
            return SpanIndexParams.DEFAULT;
        }
        return mSpanSizeLookup.getCachedSpanIndex(adapterPosition);
    }


    private int getSpanCount() {
        return mSpanSize.main * mSpanSize.second;
    }

    @Override
    void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, RecyclerView.LayoutManager.LayoutPrefetchRegistry layoutPrefetchRegistry) {
//        mSpanSizeLookup.invalidateCache();
        int groupIndex = -1;
        int count = 0;
        int spanCount = getSpanCount();
        while (count < spanCount && layoutState.hasMore(state)) {
            final int pos = layoutState.mCurrentPosition;
            layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
            final SpanIndexParams spanSize = mSpanSizeLookup.getCachedSpanIndex(pos);
            if (spanSize.mainSpan > mSpanSize.main) {
                throw new IllegalArgumentException("Item at position " + pos + " requires " + spanSize.mainSpan + " spans but GridLayoutManager has only " + mSpanSize.main + " spans main.");
            }
            if (spanSize.secondSpan > mSpanSize.second) {
                throw new IllegalArgumentException("Item at position " + pos + " requires " + spanSize.secondSpan + " spans but GridLayoutManager has only " + mSpanSize.second + " spans second.");
            }
            if (groupIndex < 0) {
                groupIndex = spanSize.groupIndex;
            } else if (groupIndex != spanSize.groupIndex) {
                break;
            }
            layoutState.mCurrentPosition += layoutState.mItemDirection;
            count++;
        }
    }

    @Override
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
        final int otherDirSpecMode = mOrientationHelper.getModeInOther();
        final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
        final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanSize.main] : 0;
        // if grid layout's dimensions are not specified, let the new row change the measurements
        // This is not perfect since we not covering all rows but still solves an important case
        // where they may have a header row which should be laid out according to children.
        if (flexibleInOtherDir) {
            updateMeasurements(); //  reset measurements
        }
        final boolean layingOutInPrimaryDirection = layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;//rtl？
        int groupIndex = -1;
        int count = 0;
        int spanCount = getSpanCount();
        while (count < spanCount && layoutState.hasMore(state)) {
            int pos = layoutState.mCurrentPosition;
            final SpanIndexParams spanSize = getSpanIndex(recycler, state, pos);
            if (spanSize.mainSpan > mSpanSize.main) {
                throw new IllegalArgumentException("Item at position " + pos + " requires " + spanSize.mainSpan + " spans but GridLayoutManager has only " + mSpanSize.main + " spans main.");
            }
            if (spanSize.secondSpan > mSpanSize.second) {
                throw new IllegalArgumentException("Item at position " + pos + " requires " + spanSize.secondSpan + " spans but GridLayoutManager has only " + mSpanSize.second + " spans second.");
            }
            if (groupIndex < 0) {
                groupIndex = spanSize.groupIndex;
            } else if (groupIndex != spanSize.groupIndex) {
                break;
            }

            View view = layoutState.next(recycler);
            if (view == null) {
                break;
            }
            mSet[count] = view;
            count++;
        }

        if (count == 0) {
            result.mFinished = true;
            return;
        }

        int maxSize = 0;
        int maxSecondSpan = 0;
        float maxSizeInOther = 0; // use a float to get size per span

        // we should assign spans before item decor offsets are calculated
        assignSpans(recycler, state, count, layingOutInPrimaryDirection);

        SparseIntArray mainSizeTotal = new SparseIntArray(mSpanSize.main); // layoutChunk 单个 item 总高度
        for (int i = 0; i < count; i++) {//挂载 item 并获取 item 的宽高
            View view = mSet[i];
            if (layoutState.mScrapList == null) {
                if (layingOutInPrimaryDirection) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                if (layingOutInPrimaryDirection) {
                    addDisappearingView(view);
                } else {
                    addDisappearingView(view, 0);
                }
            }
            calculateItemDecorationsForChild(view, mDecorInsets);

            measureChild(view, otherDirSpecMode, false);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            SpanIndexParams spanIndex = lp.mSpanIndex;
            int size = mOrientationHelper.getDecoratedMeasurementMulti(view) + mainSizeTotal.get(spanIndex.mainIndex, 0);
            mainSizeTotal.put(spanIndex.mainIndex, size);//每行chunk <列index,所有item高度和>
            if (size > maxSize) {
                maxSize = size;
            }
            int span = spanIndex.secondSpan + spanIndex.secondIndex;//计算方法与 secondBorders[spanIndex.secondIndex + spanIndex.secondSpan]; 对齐，看还有没有crash https://bugly.qq.com/v2/crash-reporting/crashes/bef1892756
            if (span > maxSecondSpan) {
                maxSecondSpan = span;
            }
            final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) / spanIndex.mainSpan;
            if (otherSize > maxSizeInOther) {
                maxSizeInOther = otherSize;
            }
        }
        if (flexibleInOtherDir) {//与orientation垂直方向是 wrap_content 算大小
            // re-distribute columns
            guessMeasurement(maxSizeInOther, currentOtherDirSize);
            // now we should re-measure any item that was match parent.

            maxSize = 0;
            mainSizeTotal.clear(); //重置 add dsr
            for (int i = 0; i < count; i++) {
                View view = mSet[i];
                measureChild(view, View.MeasureSpec.EXACTLY, true);
                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
                int cur = mainSizeTotal.get(lp.mSpanIndex.mainIndex, 0);
                int size = mOrientationHelper.getDecoratedMeasurementMulti(view) + cur;
                mainSizeTotal.put(lp.mSpanIndex.mainIndex, size);
                if (size > maxSize) {
                    maxSize = size;
                }
            }
        }
        int[] secondBorders = null;
        // Views that did not measure the maxSize has to be re-measured
        // We will stop doing this once we introduce Gravity in the GLM layout params
        for (int i = 0; i < count; i++) {//height+margin+decoration 相等，否则下一行对不齐。原有逻辑拉高 height 改为拉高 decoration
            final View view = mSet[i];
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            SpanIndexParams spanIndex = lp.mSpanIndex;
            if (spanIndex.secondSpan == maxSecondSpan) {//mSpanSize.second
                lp.marginExtraStart = 0;
                lp.marginExtraEnd = 0;
            } else {
                if (secondBorders == null) {
                    secondBorders = calculateSecondBorders(maxSecondSpan, maxSize);//这里必须传 maxSecondSpan 可能有单行小卡
                }
                int curStart = secondBorders[spanIndex.secondIndex];
                int curEnd = secondBorders[spanIndex.secondIndex + spanIndex.secondSpan];
                lp.marginExtraStart = curStart;
                lp.marginExtraEnd = maxSize - curEnd;
            }
            if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
                final Rect decorInsets = lp.mDecorInsets;

                final int verticalInsets;
                final int horizontalInsets;
                if (mOrientation == VERTICAL) {//item secondSpan 的上下填充 ItemDecoration ，保证每个 item 高度不变的情况下 height+margin+decoration 相等
                    verticalInsets = decorInsets.top + decorInsets.bottom + lp.topMargin + lp.bottomMargin + lp.marginExtraStart + lp.marginExtraEnd;
                    horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin;
                } else {
                    verticalInsets = decorInsets.top + decorInsets.bottom + lp.topMargin + lp.bottomMargin;
                    horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin + lp.marginExtraStart + lp.marginExtraEnd;
                }
                final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex.mainIndex, lp.mSpanIndex.mainSpan);
                final int wSpec;
                final int hSpec;
                if (mOrientation == VERTICAL) {
                    wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, horizontalInsets, lp.width, false);
                    hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets, View.MeasureSpec.EXACTLY);
                } else {
                    wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets, View.MeasureSpec.EXACTLY);
                    hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, verticalInsets, lp.height, false);
                }
                measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
            }
            Log.i(TAG, "layoutChunk maxSize " + getPosition(view) + " " + maxSize + " " + lp.marginExtraStart + " " + lp.marginExtraEnd);
        }

        result.mConsumed = maxSize;

        int left = 0, right = 0, top = 0, bottom = 0;
        for (int i = 0; i < count; i++) {//layout
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            SpanIndexParams spanIndex = params.mSpanIndex;
            if (mOrientation == VERTICAL) {
                if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {//往上滑
                    bottom = layoutState.mOffset - params.marginExtraEnd;
                    top = layoutState.mOffset - maxSize + params.marginExtraStart;
                } else {//往下滑
                    top = layoutState.mOffset + params.marginExtraStart;
                    bottom = layoutState.mOffset + maxSize - params.marginExtraEnd;
                }
            } else {
                if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                    right = layoutState.mOffset - params.marginExtraEnd;
                    left = layoutState.mOffset - maxSize + params.marginExtraStart;
                } else {
                    left = layoutState.mOffset + params.marginExtraStart;
                    right = layoutState.mOffset + maxSize - params.marginExtraEnd;
                }
            }

            if (mOrientation == VERTICAL) {
                if (isLayoutRTL()) {
                    right = getPaddingLeft() + mCachedBorders[mSpanSize.main - spanIndex.mainIndex];
                    left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
                } else {
                    left = getPaddingLeft() + mCachedBorders[spanIndex.mainIndex];
                    right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
                }
            } else {
                top = getPaddingTop() + mCachedBorders[spanIndex.mainIndex];
                bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            // We calculate everything with View's bounding box (which includes decor and margins)
            // To calculate correct layout position, we subtract margins.
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            if (DEBUG) {
                Log.d(TAG, "laid out child at position " + getPosition(view) + ", offset:" + layoutState.mOffset + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin) + ", span:" + params.mSpanIndex + ", start:" + params.marginExtraStart + ", end:" + params.marginExtraEnd);
            }
            // Consume the available space if the view is not removed OR changed
            if (params.isItemRemoved() || params.isItemChanged()) {
                result.mIgnoreConsumed = true;
            }
            result.mFocusable |= view.hasFocusable();
        }
        Arrays.fill(mSet, null);
    }

    /**
     * Measures a child with currently known information. This is not necessarily the child's final
     * measurement. (see fillChunk for details).
     *
     * @param view                   The child view to be measured
     * @param otherDirParentSpecMode The RV measure spec that should be used in the secondary
     *                               orientation
     * @param alreadyMeasured        True if we've already measured this view once
     */
    private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) {
        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
        final Rect decorInsets = lp.mDecorInsets;
        final int verticalInsets = decorInsets.top + decorInsets.bottom + lp.topMargin + lp.bottomMargin;
        final int horizontalInsets = decorInsets.left + decorInsets.right + lp.leftMargin + lp.rightMargin;
        final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex.mainIndex, lp.mSpanIndex.mainSpan);
        final int wSpec;
        final int hSpec;
        if (mOrientation == VERTICAL) {
            wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, horizontalInsets, lp.width, false);
            hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), verticalInsets, lp.height, true);
        } else {
            hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, verticalInsets, lp.height, false);
            wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(), horizontalInsets, lp.width, true);
        }
        measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured);
    }

    /**
     * This is called after laying out a row (if vertical) or a column (if horizontal) when the
     * RecyclerView does not have exact measurement specs.
     * <p>
     * Here we try to assign a best guess width or height and re-do the layout to update other
     * views that wanted to MATCH_PARENT in the non-scroll orientation.
     *
     * @param maxSizeInOther      The maximum size per span ratio from the measurement of the children.
     * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below.
     */
    private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) {
        final int contentSize = Math.round(maxSizeInOther * mSpanSize.main);
        // always re-calculate because borders were stretched during the fill
        calculateItemBorders(Math.max(contentSize, currentOtherDirSize));
    }

    private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, boolean alreadyMeasured) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
        final boolean measure;
        if (alreadyMeasured) {
            measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
        } else {
            measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
        }
        if (measure) {
            child.measure(widthSpec, heightSpec);
        }
    }

    private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, boolean layingOutInPrimaryDirection) {
        // spans are always assigned from 0 to N no matter if it is RTL or not.
        // RTL is used only when positioning the view.
        int start, end, diff;
        // make sure we traverse from min position to max position
        if (layingOutInPrimaryDirection) {
            start = 0;
            end = count;
            diff = 1;
        } else {
            start = count - 1;
            end = -1;
            diff = -1;
        }
        for (int i = start; i != end; i += diff) {
            View view = mSet[i];
            LayoutParams params = (LayoutParams) view.getLayoutParams();
            params.mSpanIndex = getSpanIndex(recycler, state, getPosition(view));
        }
    }


    public SpanSize getSpanSize() {
        return mSpanSize;
    }


    /**
     * Sets the number of spans to be laid out.
     * <p>
     * If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
     * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
     */
    public void setSpanCount(SpanSize spanCount) {
        if (spanCount == null) {
            throw new IllegalArgumentException("Span count should not be null");
        }
        if (spanCount.main < 1) {
            throw new IllegalArgumentException("Span main should be at least 1. Provided " + spanCount.main);
        }
        if (spanCount.second < 1) {
            throw new IllegalArgumentException("Span second should be at least 1. Provided " + spanCount.second);
        }
        if (spanCount.equals(mSpanSize)) {
            return;
        }
        mPendingSpanCountChange = true;

        mSpanSize = spanCount;
        mSpanSizeLookup.invalidateCache();
        requestLayout();
    }

    @Override
    public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
        View prevFocusedChild = findContainingItemView(focused);
        if (prevFocusedChild == null) {
            return null;
        }
        LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams();
        SpanIndexParams spanIndex = lp.mSpanIndex;
        final int prevSpanStart = spanIndex.mainIndex;
        final int prevSpanEnd = spanIndex.mainIndex + spanIndex.mainSpan;
        View view = super.onFocusSearchFailed(focused, focusDirection, recycler, state);
        if (view == null) {
            return null;
        }
        // LinearLayoutManager finds the last child. What we want is the child which has the same
        // spanIndex.
        final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
        final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout;
        final int start, inc, limit;
        if (ascend) {
            start = getChildCount() - 1;
            inc = -1;
            limit = -1;
        } else {
            start = 0;
            inc = 1;
            limit = getChildCount();
        }
        final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL();

        // The focusable candidate to be picked if no perfect focusable candidate is found.
        // The best focusable candidate is the one with the highest amount of span overlap with
        // the currently focused view.
        View focusableWeakCandidate = null; // somewhat matches but not strong
        int focusableWeakCandidateSpanIndex = -1;
        int focusableWeakCandidateOverlap = 0; // how many spans overlap

        // The unfocusable candidate to become visible on the screen next, if no perfect or
        // weak focusable candidates are found to receive focus next.
        // We are only interested in partially visible unfocusable views. These are views that are
        // not fully visible, that is either partially overlapping, or out-of-bounds and right below
        // or above RV's padded bounded area. The best unfocusable candidate is the one with the
        // highest amount of span overlap with the currently focused view.
        View unfocusableWeakCandidate = null; // somewhat matches but not strong
        int unfocusableWeakCandidateSpanIndex = -1;
        int unfocusableWeakCandidateOverlap = 0; // how many spans overlap

        // The span group index of the start child. This indicates the span group index of the
        // next focusable item to receive focus, if a focusable item within the same span group
        // exists. Any focusable item beyond this group index are not relevant since they
        // were already stored in the layout before onFocusSearchFailed call and were not picked
        // by the focusSearch algorithm.
        int focusableSpanGroupIndex = getSpanIndex(recycler, state, start).groupIndex;
        for (int i = start; i != limit; i += inc) {
            int spanGroupIndex = getSpanIndex(recycler, state, i).groupIndex;
            View candidate = getChildAt(i);
            if (candidate == prevFocusedChild) {
                break;
            }

            if (candidate.hasFocusable() && spanGroupIndex != focusableSpanGroupIndex) {
                // We are past the allowable span group index for the next focusable item.
                // The search only continues if no focusable weak candidates have been found up
                // until this point, in order to find the best unfocusable candidate to become
                // visible on the screen next.
                if (focusableWeakCandidate != null) {
                    break;
                }
                continue;
            }

            final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams();
            final int candidateStart = candidateLp.mSpanIndex.mainIndex;
            final int candidateEnd = candidateLp.mSpanIndex.mainIndex + candidateLp.mSpanIndex.mainSpan;
            if (candidate.hasFocusable() && candidateStart == prevSpanStart && candidateEnd == prevSpanEnd) {
                return candidate; // perfect match
            }
            boolean assignAsWeek = false;
            if ((candidate.hasFocusable() && focusableWeakCandidate == null) || (!candidate.hasFocusable() && unfocusableWeakCandidate == null)) {
                assignAsWeek = true;
            } else {
                int maxStart = Math.max(candidateStart, prevSpanStart);
                int minEnd = Math.min(candidateEnd, prevSpanEnd);
                int overlap = minEnd - maxStart;
                if (candidate.hasFocusable()) {
                    if (overlap > focusableWeakCandidateOverlap) {
                        assignAsWeek = true;
                    } else if (overlap == focusableWeakCandidateOverlap && preferLastSpan == (candidateStart > focusableWeakCandidateSpanIndex)) {
                        assignAsWeek = true;
                    }
                } else if (focusableWeakCandidate == null && isViewPartiallyVisible(candidate, false, true)) {
                    if (overlap > unfocusableWeakCandidateOverlap) {
                        assignAsWeek = true;
                    } else if (overlap == unfocusableWeakCandidateOverlap && preferLastSpan == (candidateStart > unfocusableWeakCandidateSpanIndex)) {
                        assignAsWeek = true;
                    }
                }
            }

            if (assignAsWeek) {
                if (candidate.hasFocusable()) {
                    focusableWeakCandidate = candidate;
                    focusableWeakCandidateSpanIndex = candidateLp.mSpanIndex.mainIndex;
                    focusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) - Math.max(candidateStart, prevSpanStart);
                } else {
                    unfocusableWeakCandidate = candidate;
                    unfocusableWeakCandidateSpanIndex = candidateLp.mSpanIndex.mainIndex;
                    unfocusableWeakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) - Math.max(candidateStart, prevSpanStart);
                }
            }
        }
        return (focusableWeakCandidate != null) ? focusableWeakCandidate : unfocusableWeakCandidate;
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return mPendingSavedState == null && !mPendingSpanCountChange;
    }

    /**
     * LayoutParams used by MultiGridLayoutManager.
     * <p>
     * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
     * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
     * expected to fill all of the space given to it.
     */
    public static class LayoutParams extends RecyclerView.LayoutParams {

        /**
         * Span Id for Views that are not laid out yet.
         */
        public static final int INVALID_SPAN_ID = -1;
        int marginExtraStart;
        int marginExtraEnd;

        SpanIndexParams mSpanIndex;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(RecyclerView.LayoutParams source) {
            super(source);
        }
    }


    /**
     * A helper class to provide the number of spans each item occupies.
     * <p>
     * Default implementation sets each item to occupy exactly 1 span.
     *
     * @see #setSpanSizeLookup(SpanSizeLookup)
     */
    public abstract static class SpanSizeLookup {
        public static SpanSize DEFAULT_SPAN = new SpanSize(1, 1);

        final SparseArray<SpanIndexParams> mSpanIndexCache = new SparseArray<>();
        private MultiGridLayoutManager layoutManager;


        /**
         * Clears the span index cache. GridLayoutManager automatically calls this method when
         * adapter changes occur.
         */
        public void invalidateCache() {
            mSpanIndexCache.clear();
        }


        public SpanIndexParams getCachedSpanIndex(int position) {
            SpanIndexParams spanIndexParams = mSpanIndexCache.get(position);
            if (spanIndexParams != null) {
                return spanIndexParams;
            }

            SpanSize spanSize = getSpanSize(position);
            if (position == 0) {
                SpanIndexParams indexParams = new SpanIndexParams(spanSize, 0, 0, 0);
                mSpanIndexCache.put(0, indexParams);
                return indexParams;
            }

            SpanIndexParams cachedSpanIndex = getCachedSpanIndex(position - 1);
            int targetSecondIndex = cachedSpanIndex.secondIndex + cachedSpanIndex.secondSpan;
            int targetMainIndex;
            int targetGroupIndex;

            if (targetSecondIndex + spanSize.second > layoutManager.getSpanSize().second) {//同一列放不下新的 item 放到下一列
                targetSecondIndex = 0;
                targetMainIndex = cachedSpanIndex.mainIndex + cachedSpanIndex.mainSpan;
                if (targetMainIndex + spanSize.main > layoutManager.getSpanSize().main) {//超出当前列，放到下一列
                    targetMainIndex = 0;
                    targetGroupIndex = cachedSpanIndex.groupIndex + 1;
                } else {
                    targetGroupIndex = cachedSpanIndex.groupIndex;
                }
            } else {//同一列
                targetMainIndex = cachedSpanIndex.mainIndex;
                targetGroupIndex = cachedSpanIndex.groupIndex;
            }
            SpanIndexParams indexParams = new SpanIndexParams(spanSize, targetGroupIndex, targetMainIndex, targetSecondIndex);
            mSpanIndexCache.put(position, indexParams);
            return indexParams;
        }

        protected abstract SpanSize getSpanSize(int position);
    }

    public static class SpanSize {
        public static final SpanSize DEFAULT = new SpanSize(1, 1);
        public final int main;
        public final int second;

        public SpanSize(int main, int second) {
            this.main = main;
            this.second = second;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof SpanSize)) return false;
            SpanSize spanSize = (SpanSize) o;
            return main == spanSize.main && second == spanSize.second;
        }

        @Override
        public String toString() {
            return "main=" + main + ", second=" + second;
        }
    }


    public static class SpanIndexParams {
        public static final SpanIndexParams DEFAULT = new SpanIndexParams(SpanSize.DEFAULT, 0, 0, 0);
        public final int mainSpan;//横轴方向 span 数
        public final int secondSpan;//竖轴方向 span 数
        public final int groupIndex;//所在 recyclerView 竖轴 layoutChunk 的 index
        public final int mainIndex;//横轴方向 item 的 index
        public final int secondIndex;//横轴方向内的竖轴 item 的 index

        public SpanIndexParams(SpanSize span, int groupIndex, int mainIndex, int secondIndex) {
            this.mainSpan = span.main;
            this.secondSpan = span.second;
            this.groupIndex = groupIndex;
            this.mainIndex = mainIndex;
            this.secondIndex = secondIndex;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof SpanIndexParams)) return false;
            SpanIndexParams that = (SpanIndexParams) o;
            return mainSpan == that.mainSpan && secondSpan == that.secondSpan && groupIndex == that.groupIndex && mainIndex == that.mainIndex && secondIndex == that.secondIndex;
        }

        @Override
        public String toString() {
            return "group=" + groupIndex + ", main=" + mainIndex + "," + mainSpan + ", second=" + secondIndex + "," + secondSpan;
        }
    }
}
